﻿using net.xBei.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace net.xBei.Helper {
    /// <summary>
    /// 日期范围
    /// </summary>
    public static class DateRangeHelper {
        public static async Task<Dictionary<DateRangeTypes, T>> StatisticsByDateRangeAsync<T>(this IEnumerable<DateRangeTypes> ranges,
                                                                                              Func<DateRangeTypes, DateTime?, DateTime?, Task<T>> compute,
                                                                                              int timezoneOffset = 0) {
            if (null == ranges || !ranges.Any()) {
                ranges = [DateRangeTypes.All];
            }
            return await ranges.ToDictionaryAsync(
                rangeType => rangeType,
                async rangeType => {
                    var range = rangeType.GetDateRange(timezoneOffset);
                    return await compute(rangeType, range?[0], range?[1]);
                });
        }
        public static Dictionary<DateRangeTypes, T> StatisticsByDateRange<T>(this IEnumerable<DateRangeTypes> ranges,
                                                                             Func<DateRangeTypes, DateTime?, DateTime?, T> compute,
                                                                             int timezoneOffset = 0) {
            if (null == ranges || !ranges.Any()) {
                ranges = [DateRangeTypes.All];
            }
            return ranges.ToDictionary(
                rangeType => rangeType,
                rangeType => {
                    var range = rangeType.GetDateRange(timezoneOffset);
                    return compute(rangeType, range?[0], range?[1]);
                });
        }
        public static Dictionary<long, T> StatisticsByDateSlice<T>(this DateSliceTypes slice,
                                                                   long start,
                                                                   long end,
                                                                   Func<long, long, Dictionary<long, T>> collect,
                                                                   Func<IEnumerable<T>, T> aggregate) {
            var results = new Dictionary<long, T>();
            var datetimes = slice.GetDatesBySlice(start, end).ToArray();
            var source = collect(start, end);
            for (var i = 0; i < datetimes.Length; i++) {
                var s = datetimes[i];
                var e = ((i + 1) < datetimes.Length) ? datetimes[i + 1] : long.MaxValue;
                results[s] = aggregate(source.Where(t => t.Key >= s && t.Key < e).Select(t => t.Value));
            }
            return results;
        }
        /// <summary>
        /// 获取日期范围
        /// </summary>
        /// <param name="type">日期范围类型</param>
        /// <param name="timezoneOffset">时区偏移量（分钟）</param>
        /// <returns>[0:起始, 1:结束]</returns>
        public static DateTime[]? GetDateRange(this DateRangeTypes type, int timezoneOffset = 0) {
            var baseDate = DateTime.UtcNow.Date.AddMinutes(timezoneOffset);
            switch (type) {
                case DateRangeTypes.Today:
                    return [
                        baseDate,
                        baseDate.AddDays(1)
                    ];
                case DateRangeTypes.Yesterday:
                    return [
                        baseDate.AddDays(-1),
                        baseDate
                    ];
                case DateRangeTypes.InWeek: {
                        var start = baseDate.AddDays(-(int)baseDate.DayOfWeek);
                        return [
                            start,
                            start.AddDays(7)
                        ];
                    }
                case DateRangeTypes.LastWeek: {
                        var end = baseDate.AddDays(-(int)baseDate.DayOfWeek);
                        return [
                            end.AddDays(-7),
                            end
                        ];
                    }
                case DateRangeTypes.InMonth: {
                        var start = baseDate.AddDays(1 - baseDate.Day);
                        return [
                            start,
                            start.AddMonths(1)
                        ];
                    }
                case DateRangeTypes.LastMonth: {
                        var end = baseDate.AddDays(1 - baseDate.Day);
                        return [
                            end.AddMonths(-1),
                            end
                        ];
                    }
                case DateRangeTypes.InYear: {
                        var start = baseDate.AddDays(1 - baseDate.DayOfYear);
                        return [
                            start,
                            start.AddYears(1)
                        ];
                    }
                case DateRangeTypes.LastYear: {
                        var end = baseDate.AddDays(1 - baseDate.DayOfYear);
                        return [
                            end.AddYears(-1),
                            end
                        ];
                    }
                case DateRangeTypes.All:
                    return null;
                default:
                    throw new ArgumentOutOfRangeException(nameof(type));
            }
        }
        /// <summary>
        /// 根据时间切片类型获取日期列表
        /// Note 此处不考虑时区偏移，实际使用时应在数据计算时反向处理时区偏移
        /// </summary>
        /// <param name="type">时间切片类型</param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns>[start, end]区间按照时间切片类型间隔的日期列表</returns>
        public static IEnumerable<long> GetDatesBySlice(this DateSliceTypes type, long start, long end) {
            var dateStart = start.FromUnixStamp();//.ToDateTimeFromUnixStamp();
            var dateEnd = end.FromUnixStamp();
            switch (type) {
                case DateSliceTypes.Day:
                    while (dateStart <= dateEnd) {
                        yield return dateStart.ToUnixStampDateTime();
                        dateStart = dateStart.AddDays(1);
                    }
                    break;
                case DateSliceTypes.Week:
                    yield return dateStart.ToUnixStampDateTime();
                    dateStart = dateStart.AddDays(7 - (int)dateStart.DayOfWeek);
                    while (dateStart <= dateEnd) {
                        yield return dateStart.ToUnixStampDateTime();
                        dateStart = dateStart.AddDays(7);
                    }
                    break;
                case DateSliceTypes.Month:
                    yield return dateStart.ToUnixStampDateTime();
                    dateStart = dateStart.AddDays(1 - dateStart.Day).AddMonths(1);
                    while (dateStart <= dateEnd) {
                        yield return dateStart.ToUnixStampDateTime();
                        dateStart = dateStart.AddMonths(1);
                    }
                    break;
                case DateSliceTypes.Year:
                    yield return dateStart.ToUnixStampDateTime();
                    dateStart = dateStart.AddDays(1 - dateStart.DayOfYear).AddYears(1);
                    while (dateStart <= dateEnd) {
                        yield return dateStart.ToUnixStampDateTime();
                        dateStart = dateStart.AddYears(1);
                    }
                    break;
            }
        }
    }
}
